/*
	Copyright (c) 2009, M.P. Bartnig & A. Gosman
	
	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:
	
	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.
	
	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
*/
package nl.sanity.managers
{
	import flash.display.Stage;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	
	/**
	 * KeyManager
	 *
	 * @langversion ActionScript 3
	 *
	 * @author A. Gosman
	 * @since  05/08/2009
	 */
	public class KeyManager
	{
		private static var IS_INITIALIZED	: Boolean;
		private static var STAGE			: Stage;
		private static var KEYS				: Object;
		private static var COMBINATIONS		: Object;
		
		
		public function KeyManager()
		{
			throw new Error( "KeyManager only provides static methods and should not be instantiated" );
		}
		
		
		/**
         * Initializes the KeyManager class.
         * 
         * 	@param stage:Stage 		An instance of the stage property of the application. Needed for adding KeyboardEvent listeners.
         */
        public static function initialize( stage:Stage ):void 
        {
        	if ( !stage )
        	{
        		throw new Error( "[KeyManager]: stage property must be non-null." );	
        	}
        	
            if ( !KeyManager.IS_INITIALIZED ) 
            {
            	KeyManager.STAGE			= stage;
            	KeyManager.IS_INITIALIZED	= true;
                KeyManager.KEYS 			= new Object();
                KeyManager.COMBINATIONS		= new Object();
                
                KeyManager.addEventListeners();
            }
        }
        
        /**
        * 	Call this function when monitoring of keyboard input is no longer needed.
        * 	Removes listeners and disposes any keyboard combinations that were being monitored.
        */
        public static function dispose():void
        {
        	if ( KeyManager.IS_INITIALIZED ) 
            {
	        	KeyManager.removeEventListeners();
	        	KeyManager.KEYS 			= null;
	        	KeyManager.COMBINATIONS		= null;
	        	KeyManager.IS_INITIALIZED	= false;
	        }
        }
        
        /**
         * Returns whether the key represented by the specified keyCode is pressed or not.
         */
        public static function isDown( keyCode:uint ):Boolean 
        {
            if ( !KeyManager.IS_INITIALIZED ) 
            {
                throw new Error("KeyManager has not yet been initialized.");
            }
            
            return Boolean( keyCode in KeyManager.KEYS );
        }
        
        /**
        * 	Add a keycombination that should be monitored.
        * 
        * 	@param 	callBack		The callBack function that gets called when the given keycombination is pressed.
        * 	@return	Object			The object that is created and stored inside the KeyManager.COMBINATIONS Object. 
        * 
        */
        public static function addCombination( id:String, callBack:Function, ...combination:Array ):Object 
        {
        	if( id in KeyManager.COMBINATIONS )
        	{
        		throw new Error( "A combination with id '" + id + "' already exists" );
        	}
        	
        	for( var i:uint = 0; i < combination.length; i++ )
        	{
        		if( !(combination[ i ] is uint ) )
        		{
        			throw new Error( "Invalid key combination provided" );
        		}
        	}
        	
        	var keyCombination:Object		= { callBack: callBack, keys: combination };
        	KeyManager.COMBINATIONS[ id ] 	= keyCombination;
        	
        	return keyCombination;
        }
        
        public static function removeCombination( id:String ):void 
        {
        	if( !( id in KeyManager.COMBINATIONS ) )
        	{
        		throw new Error( "A combination with id '" + id + "' does not exist" );
        	}
        	
        	delete KeyManager.COMBINATIONS[ id ];
        }
        
        protected static function checkKeyCombinations():void
        {
        	var keysAreDown:Boolean;
        	
        	for each( var combination:Object in KeyManager.COMBINATIONS )
        	{
        		keysAreDown	= true;
        		
        		for each( var keyCode:uint in combination.keys )
        		{
        			if( !KeyManager.isDown( keyCode ) )
        			{
        				keysAreDown	= false;
        				break;
        			}
        		}
        		
        		if( keysAreDown )
        		{
        			combination.callBack();
        		}
        	}
        }
        
        /**
        *	Assign listeners for key presses and deactivation of the player 
        * 
        */
        protected static function addEventListeners():void
        {
            KeyManager.STAGE.addEventListener( KeyboardEvent.KEY_DOWN, onKeyDown );
            KeyManager.STAGE.addEventListener( KeyboardEvent.KEY_UP, onKeyUp );
            KeyManager.STAGE.addEventListener( Event.DEACTIVATE, onDeactivate );
        }
        
        protected static function removeEventListeners():void
        {
            KeyManager.STAGE.removeEventListener( KeyboardEvent.KEY_DOWN, onKeyDown );
            KeyManager.STAGE.removeEventListener( KeyboardEvent.KEY_UP, onKeyUp );
            KeyManager.STAGE.removeEventListener( Event.DEACTIVATE, onDeactivate );
        }
        
        
        /**
        * 	Triggered when a key is pressed.
        * 	Stores the keycode inside the 'keys' Object.
        */	
        private static function onKeyDown( event:KeyboardEvent ):void 
        {
  			KeyManager.KEYS[ event.keyCode] 	= true;
  			
  			checkKeyCombinations();
        }
       
        private static function onKeyUp( event:KeyboardEvent ):void 
        {
            if ( event.keyCode in KeyManager.KEYS ) 
            {
                delete KeyManager.KEYS[ event.keyCode ];
            }
        }
       
        /**
         * Triggered when Flash Player loses operating system focus.
         * Clear all keys in keysDown since the player cannot detect keys being pressed or released when not focused
         */
        private static function onDeactivate( event:Event ):void 
        {
            KeyManager.KEYS = new Object();
        }

	}
}